package trss.bencode;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class BDecoder {
    private final Log log = LogFactory.getLog(BDecoder.class);
    
    public List<BElement> decode(InputStream is) throws IOException, BFormatException {
        List<BElement> result = new ArrayList<BElement>();
        boolean end = false;
        while(is.available()>0 && !end ){
            int c = is.read();
            switch (c) {
            case 'd':
                result.add(decodeDictionary(is));
                break;
            case 'l':
                result.add(decodeList(is));
                break;
            case 'i':
                result.add(decodeInteger(is));
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                result.add(decodeString(is, c));
                break;
            case 'e':
                end = true;
                break;
            default:
                end = true;
                break;
                //TODO
                //throw new BFormatException("Wrong bencode format of input stream");
            }
        }
        
        return result ;
        
    }

    /**
     * @param is 
     * @param c 
     * @return
     * @throws IOException 
     * @throws BFormatException 
     */
    private BString decodeString(InputStream is, int c) throws IOException, BFormatException {
        BString result = new BString();
        String length = "";
        do {
            length += (char)c;
            c = is.read();
        } while (c != ':');
        int stringLength;
        try {
            stringLength = Integer.parseInt(length);
        } catch (NumberFormatException e) {
            BFormatException formatE = new BFormatException("Can't parce string");
            formatE.initCause(e);
            throw formatE;
        }
        byte buf[] = new byte[stringLength];
        int readed = is.read(buf);
        log.debug(String.format("readed %d bytes", readed));
        result.setValue(buf);
        log.debug("Decoded string: " + result);
        return result ;
    }

    /**
     * @param is 
     * @return
     * @throws IOException 
     * @throws BFormatException 
     */
    private BInteger decodeInteger(InputStream is) throws IOException, BFormatException {
        BInteger result = new BInteger();
        int c;
        String resultString = "";
        while (true) {
            c= is.read();
            if (c != 'e') {
                resultString += (char)c;
            } else {
                break;
            }
        };
        try {
            result.setValue(Integer.parseInt(resultString));
        } catch (NumberFormatException e) {
            BFormatException formatE = new BFormatException("Can't parse integer");
            formatE.initCause(e);
            throw formatE;
        }
        log.debug("Decoded integer: " + result);
        return result;
    }

    /**
     * @param is 
     * @return
     * @throws IOException 
     * @throws BFormatException 
     */
    private BList decodeList(InputStream is) throws IOException, BFormatException {
        BList result = new BList();
        List<BElement> elements = decode(is);
        result.addAll(elements);
        /*int c = is.read();
        if (c != 'e') {
            throw new BFormatException("List don't ends with 'e'");
        }*/
        log.debug("Decoded list:" + result);
        return result ;
    }

    /**
     * @param is 
     * @return
     * @throws BFormatException 
     * @throws IOException 
     */
    private BDictionary decodeDictionary(InputStream is) throws IOException, BFormatException {
        BDictionary result = new BDictionary();
        List<BElement> elements = decode(is);
        try {
            for (int i = 0; i < elements.size(); i+=2) {
                BString key = (BString) elements.get(i);
                BElement value = elements.get(i+1);
                result.put(new String(key.getValue()), value);
            }
        } catch (Exception e) {
            BFormatException formatE = new BFormatException("Can't parse dictionary");
            formatE.initCause(e);
            throw formatE;
        }
        /*int c = is.read();
        if (c != 'e') {
            throw new BFormatException("Dictionary don't ends with 'e'");
        }*/
        log.debug("Decoded dictionary:" + result);
        return result ;
    }
}
